β

Как стать автором
Погнали в космос?
Один менеджер паролей — и ты гуру безопасности

Моя лентаВсе потоки

0

Рейтинг

Quadcode
Финтех-компания

Подписаться

mrehbr 31 мая 2023 в 20:08

Как обрабатывать ошибки в Golang – рассказываем на собственном примере

Средний

6 мин

11K

Блог компании QuadcodeGo*

Кейс

Всем привет. Меня зовут Алексей Бурмистров, я senior Golang разработчик в Quadcode. В процессе разработки биллинга, мы столкнулись с различными типами ошибок, которые могут возникать во время выполнения программы. В данной статье я хочу поделиться нашим опытом в структурировании и обработке этих ошибок, а также представить подходы, которые мы применили для их эффективной обработки и диагностики. Наша основная цель заключается в создании понятных и легко обрабатываемых ошибок, которые гарантируют надежную работу биллинга.

Ошибки это один из самых важных аспектов любого языка программирования. То, как обрабатываются ошибки, влияет на приложения многими способами. То, как определяются ошибки в Golang, немного отличается от таких языков как Java, Python, Javascript. В Go ошибки – это значения.​

Свой тип ошибки

Весь код, связанный с обработкой ошибок, размещен в корне нашего приложения. Это сделано для предотвращения конфликтов со стандартным пакетом errors. Такой подход делает обработку ошибок приложения более явной и не лишает возможности использовать стандартную библиотеку без применения алиасов.

​Основой всего является тип Error– конкретное представление ошибки, который реализует стандартный интерфейс error. Он имеет несколько полей, некоторые из которых могут быть не заданы:

type errorCode string

// Коды ошибок приложения.
const (
...
ENOTFOUND errorCode = "not_found"
EINTERNAL errorCode = "internal"
...
)

type Error struct {
// Вложенная ошибка
Err error `json:"err"`
// Дополнительный контекст ошибки
Fields map[string]interface{}
// Код ошибки.
Code errorCode `json:"code"`
// Сообщение об шибке, которое понятно пользователю.
Message string `json:"message"`
// Выполняемая операция
Op string `json:"op"`
}

Op обозначает выполняемую операцию. Оно представляет собой строку, которая содержит имя метода или функции такие как repo.User, convert, Auth.Login и так далее.

Message содержит сообщение или ключ перевода ошибки, которое можно показать пользователю.

Code – это конкретный тип ошибки. Это обеспечивает стандартизацию и однозначность в обработке ошибок, позволяя легко идентифицировать и классифицировать ошибки. Список возможных типов может быть расширен при необходимости, обеспечивая гибкость и возможность добавления новых типов ошибок по мере развития всего приложения. Еще это позволяет нам точно определить статусы ответов API, связанные с каждым типом ошибки.

Fields представляет собой данные, связанные с ошибкой. Эти данные могут содержать идентификаторы объектов, параметры запросов или любую другую информацию, которая может быть полезной для понимания причины ошибки.

Err содержит вложенную ошибку, которая может быть связана с текущей ошибкой. Это может быть ошибка, возвращаемая внешней библиотекой или наша собственная Error.

Вложенная ошибка полезна для отслеживания цепочки ошибок и построение трассировки, о которой мы поговорим ниже.

Создание ошибки

Для создания ошибки мы решили не делать отдельный конструктор, потому как структура не такая объемная. Для того чтобы разработчики не ошиблись при создании ошибки (например забыли & или не создали ошибку без Err и Message), мы используем собственный линтер для golangci-lint

Давайте рассмотрим пример. При обычном использовании мы можем вернуть ошибку в методе несколько раз, поэтому мы определим константу, условно называемую op, которая будет передаваться всем ошибкам в методе:

func (r *userRepository) User(ctx context.Context, id int) (*User, error) {
const op = "userRepository.User"
...
}

Еcли нам нужно только добавить op к ошибке для передачи на верхний уровень мы можем воспользоваться вспомогательными функциями OpError или OpErrorOrNil

...
var user User
err := db.QueryRow(ctx, query, id)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, &app.Error{Op: op,Code: app.ENOTFOUND, Message: "user not found"}
}
return app.OpError(op, err)
}
...

Обработка ошибок​

Преимуществом собственного типа ошибки стала легкость, с которой мы могли писать тесты, зависящие от ошибок, а также писать чувствительный к ошибкам код вне тестов.

Для проверки Code есть вспомогательная функция ErrorCode которая возвращает код ошибки, если это была ошибка приложения, или EINTERNAL если это была другая.

switch ErrorCode(err) {
case ENOTFOUND:
...
case EINTERNAL:
...
}

Если нам нужен полный доступ к Error можно воспользоваться стандартной библиотекой errors.

appErr := &Error{}
if errors.As(err, appErr) {
...
}

Использование поля Code позволяет понятно преобразовывать ошибки в HTTP-статус. Для этого можно создать map, где ключами будут значения Code, а значениями соответствующие HTTP-статусы.

Примерное преобразования ошибки в HTTP-статус:

var codeToHTTPStatusMap = map[errorCode]int{
ENOTFOUND: http.StatusNotFound,
EINTERNAL: http.StatusInternalServerError,
// Другие соответствия кодов ошибок и HTTP-статусов
}

func ErrCodeToHTTPStatus(err error) int {
code := ErrorCode(err)
if v, ok := codeToHTTPStatusMap[code]; ok {
return v
}

// Возвращаем стандартный HTTP-статус для неизвестных ошибок
return http.StatusInternalServerError
}

Теперь, чтобы получить соответствующий HTTP-статус для ошибки, достаточно вызвать функцию ErrCodeToHTTPStatus и передать ошибку ей. Она вернет соответствующий HTTP-статус. Если код ошибки не найден, будет возвращен стандартный HTTP-статус http.StatusInternalServerError.

Анализ и диагностика​

При анализе ошибок, возникающих в нашем приложении, мы полагаемся на информацию, которую мы записываем в логи. Однако, когда ошибки логируются в виде строк, это значительно затрудняет поиск и анализ. Так как мы структурируем логи для отправки в graylog, мы логируем ошибки в виде объектов, содержащих следующую информацию:

code: тип ошибки, чтобы понять её характер;

msg: сообщение из Err.Error();

fields: контекст, добавленный к ошибке;

trace: стек трассировки операций.

В наших логах мы избегаем логирования стандартного стека трассировки при ошибках приложения, поскольку он предоставляет недостаточно информации и его анализ затруднителен. Вот пример обычного стека трассировки:

goroutine 1 [running]:
testing.(*InternalExample).processRunResult(0xc000187aa8, {0x0, 0x0}, 0x0?, 0x0, {0x1043760e0, 0x1043b8d88})
/opt/homebrew/Cellar/go/1.19.4/libexec/src/testing/example.go:91 +0x45c
testing.runExample.func2()
/opt/homebrew/Cellar/go/1.19.4/libexec/src/testing/run_example.go:59 +0x14c
panic({0x1043760e0, 0x1043b8d88})
/opt/homebrew/Cellar/go/1.19.4/libexec/src/runtime/panic.go:890 +0x258
app.foo(...)
app/errors_test.go:336
app.bar()
app/errors_test.go:341 +0x38
app.baz()
app/errors_test.go:345 +0x24
app.ExampleTrace()
app/errors_test.go:350 +0x24
testing.runExample({{0x1042f8cd5, 0xc}, 0x1043b8528, {0x1042fcab9, 0x19}, 0x0})
/opt/homebrew/Cellar/go/1.19.4/libexec/src/testing/run_example.go:63 +0x2ec
testing.runExamples(0xc000187e00, {0x10450e080, 0x1, 0x0?})
/opt/homebrew/Cellar/go/1.19.4/libexec/src/testing/example.go:44 +0x1ec
testing.(*M).Run(0xc00014a320)
/opt/homebrew/Cellar/go/1.19.4/libexec/src/testing/testing.go:1728 +0x934
main.main()

В данном случае содержится много избыточной информации, которую сложно проанализировать. Однако стек трассировки операций выглядит следующим образом:

["ExampleRun", "baz", "bar", "foo"]

Стек трассировки операций легко читается и содержит только доменную логику, что является важным для нас.

Для логирования мы используем пакет go.uber.org/zap. Для него мы сделали вспомогательную функцию Error(err error) zap.Field, которая позволяет нам легко логировать ошибку в виде объекта.

Пример использования данной функции выглядит следующим образом:

func foo() {
...
if err != nil {
logger.Error("something gone wrong", Error(err))
}
}

Пример вывода ошибки в логе может выглядеть следующим образом:

{"level":"error","msg":"something gone wrong","error":{"msg":"user not found","code":"not_found","trace":["userRepository.User"],"fields":{"user_id":"65535"}}}

Финальные выводы

В Golang у нас есть полная свобода выбора способа обработки ошибок в наших приложениях. Однако с этой свободой приходит и большая ответственность, так как правильная обработка ошибок играет ключевую роль в надежной работе приложения. Важно понимать, что каждое приложение имеет свои особенности, и мы можем модифицировать обработку ошибок в соответствии с конкретными требованиями и контекстом.

Мы можем гибко управлять данными, содержащимися в структуре Error, и модифицировать их в соответствии с нашими потребностями. Мы можем вносить корректировки, добавлять дополнительные данные и функциональность, чтобы улучшить отслеживаемость и анализ ошибок.

Если вы хотите ознакомиться с примерами кода, вы можете найти их в GitHub.

Грамотная обработка ошибок является важной составляющей разработки приложений и нужно постоянно стремиться улучшать подход к обработке и анализу ошибок, чтобы обеспечить более надежную и удобную работу приложений.

Теги:

golang

Хабы:

Блог компании Quadcode

Go

+3

62

6
+6

Quadcode
Финтех-компания

Подписаться

Сайт

0

Карма

0

Рейтинг

@mrehbr

Golang developer

Подписаться

Комментарии 6
+6

Публикации

Лучшие за суткиПохожие

A_Tsymbalyuk 13 часов назад

Физика ошибок не прощает. Она догонит тебя на крыше и заставит платить (дважды)

Простой

6 мин

8.1K

Кейс

+40

35

25
+25

BabayMazay 12 часов назад

Самодельные французские радиолампы 1920 г. Часть 3. Разрядная трубка, лампа накаливания

Средний

9 мин

1.5K

Ретроспектива

+33

9

11
+11

Bright_Translate 8 часов назад

Как я от нечего делать замедлил Postgres в 42 000 раз

Средний

11 мин

2.6K

Кейс

Перевод

+27

20

2
+2

butivskiydm 11 часов назад

Моё мини-исследование про продолжительность жизни в России

Простой

2 мин

6.4K

Мнение

+22

17

60
+60

lokosst 13 часов назад

Выделенные серверы + Kubernetes: практическое руководство

17 мин

2.4K

Туториал

+21

23

0

JetHabr 11 часов назад

«Плохие парни работают просто»: пентестеры разбирают реальные кейсы

Средний

9 мин

2.1K

Кейс

+17

19

1
+1

k0mar0v 14 часов назад

HTC Desire HD: история, дизайн и возможности легендарного смартфона

6 мин

2.6K

+17

5

5
+5

MaFrance351 13 часов назад

Домофон Лидер-М. Как скрестить трубку с ключом и всем проиграть

Простой

4 мин

2.5K

+16

5

6
+6

psygalaxy 13 часов назад

Как мы ускорили проверку документации с помощью AI-агента: от боли к решению

Средний

25 мин

1.8K

Обзор

+16

30

9
+9

kubelet 16 часов назад

DNS-хаос, зомби-поды и майнеры в кластере: самые невероятные случаи при работе с Kubernetes

Простой

14 мин

1.2K

Кейс

+16

23

1
+1

AI и майндсет айтишников — выясняем, как изменились IT-работодатели за год
Турбо

Показать еще

Минуточку внимания

Турбо

Полный свайп: оцени, подходит ли тебе работа в IT-ритейле

Опрос

Да начнётся битва: выбираем лучший IT-бренд работодателя

Турбо

Делаем сисадмина сильнее с помощью менеджера паролей

Курсы

PRO Go. Базовый курс по структурам данных
По желаниюStepik

Магистратура Разработка IT-продукта
По факту набораНетология

Go-разработчик
По факту набораБруноям

Проектирование микросервисной архитектуры
По факту набораСлёрм

Автоматизированное тестирование веб-сервисов на Go
По факту набораOTUS

Больше курсов на Хабр Карьере

Информация

Сайт

jobs.quadcode.com

Дата регистрации

26 мая 2021

Численность

201–500 человек

Блог на Хабре

31 мая 2023 в 20:57

Бизнес хочет свой блог. Стоит ли начинать? Наша прощальная статья на Хабр

15K

64
+64

31 мая 2023 в 20:08

Как обрабатывать ошибки в Golang – рассказываем на собственном примере

11K

6
+6

5 мая 2023 в 16:18

Как появляются дизайн-системы: рассказываем и показываем на собственном примере

5.3K

9
+9

21 апр 2023 в 14:00

5 самых дорогостоящих ошибок ПО в истории: как потерять миллиарды долларов из-за неосторожности или невнимательности

8.2K

1
+1

10 апр 2023 в 17:36

Мы тоже отправили сотрудникам компании фишинговое письмо

5.4K

20
+20

Истории

История Яндекс Почты

С Днём сисадмина!

Как расти в ИТ: советы, гайды и опыт сеньоров

1 055 450 очков в облаке – попробуй обогнать

Годнота из блогов компаний

Реклама

ООО «Хабр»

ИНН 7705756279

erid: 2VSb5wN9Qsb

IT в ритейле

Свайпни и узнай, твоё ли это

Ваш аккаунт

Профиль

Трекер

Диалоги

Настройки

ППА

Разделы

Статьи

Новости

Хабы

Компании

Авторы

Песочница

Информация

Устройство сайта

Для авторов

Для компаний

Документы

Соглашение

Конфиденциальность

Услуги

Корпоративный блог

Медийная реклама

Нативные проекты

Образовательные программы

Стартапам

Настройка языкаТехническая поддержка
© 2006–2025, Habr